
/*
 * Copyright (C) 2009 Archie L. Cobbs. All rights reserved.
 *
 * $Id: XMLArrayField.java 377 2010-03-11 20:50:18Z archie.cobbs $
 */

package org.dellroad.sidekar.xml;

import java.lang.reflect.Array;
import java.util.Iterator;

import org.dellroad.sidekar.ArrayField;
import org.dellroad.sidekar.SimpleField;
import org.dellroad.sidekar.util.SidekarException;
import org.dellroad.sidekar.util.SimpleIterator;

/**
 * Container for a {@link ArrayField}'s value during the Sidekar XML import/export process.
 */
class XMLArrayField<E> extends XMLSimpleField<Object> {

    private Object array;
    private int length;
    private SimpleField<E> elementField;
    private boolean isNull;

    private int index;

    // Used during unmarshalling (for fields of items)
    private XMLArrayField(XMLItem xmlItem) {
        super(xmlItem);
        this.length = -1;
    }

    // Used during unmarshalling (for nested fields)
    private XMLArrayField(XMLField xmlField) {
        super(xmlField);
        this.length = -1;
    }

    // Used during marshalling
    public XMLArrayField(ArrayField<E> field, Object array) {
        super(field.getName());
        this.array = array;
        this.length = this.array != null ? Array.getLength(this.array) : -1;
        this.elementField = field.getElementField();
        this.isNull = array == null;
    }

    // Used during unmarshalling
    @Override
    public Object getObjectValue() {
        return getArray();
    }

    // Used during marshalling
    @Override
    public boolean hasDefaultValue() {
        return this.array == null;
    }

    // Used during unmarshalling
    @Override
    protected SimpleField<?> getNestedField() {
        getArray();
        return this.elementField;
    }

    @SuppressWarnings("unchecked")
    private Object getArray() {
        if (!this.isNull && this.array == null && this.length >= 0) {
            ArrayField<E> field = (ArrayField<E>)getField();
            this.elementField = field.getElementField();
            Class<? extends E[]> arrayType = ArrayField.getArrayType(this.elementField.getType());
            this.array = Array.newInstance(arrayType.getComponentType(), this.length);
        }
        return this.array;
    }

    // Used during unmarshalling
    public void add(XMLSimpleField<?> xmlField) {
        getArray();
        if (this.index >= this.length)
            throw new SidekarException("array length is " + this.length + " but more than " + this.length + " elements were given");
        Array.set(this.array, this.index++, xmlField.getObjectValue());
    }

    // Used during marshalling
    public Iterator<XMLSimpleField<?>> iterator() {
        return new SimpleIterator<XMLSimpleField<?>>() {

            private int index;

            public boolean hasNext() {
                return this.index < XMLArrayField.this.length;
            }

            @Override
            protected XMLSimpleField<?> getNext() {
                Object element = Array.get(XMLArrayField.this.array, this.index++);
                XMLSimpleFieldSwitch fieldSwitch = new XMLSimpleFieldSwitch(element);
                XMLArrayField.this.elementField.visit(fieldSwitch);
                return fieldSwitch.getXMLSimpleField();
            }
        };
    }

    // Used during unmarshalling
    @Override
    public <E2> void caseArrayField(ArrayField<E2> field) {
        field.set(getItem(), this.array);
    }

    // Used during unmarshalling
    public static <E> XMLArrayField<E> newInstance(Object obj) {
        return obj instanceof XMLItem ? new XMLArrayField<E>((XMLItem)obj) : new XMLArrayField<E>((XMLField)obj);
    }
}
